﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Collections.Generic;
using System.Collections;

namespace WikiDoc.Formatter
{
    /// <summary>
    /// 格式化为 XmlDoc 标准文本。
    /// </summary>
    /// <remarks>
    /// 根据 MS 建议的 XML 注释标记所表明的，XmlDoc 标准标记和 Html 标记并不完全相同：
    ///     × 段落标记是 para 而不是 p
    ///     × 列表以及表格标记使用 list type="bullet" | "number" | "table" 而不是 ul, ol, table
    ///     × 代码段使用 code 标记，而不是 pre 标记
    /// 
    /// 换行规则：
    ///  × 除特别指出的以外，一般结束标签后都换行
    ///  × 这些结束标签前换行
    ///     × code
    ///  × UL OL 开始标签前换行，如果是嵌套的列表
    ///  × 这些开始标签后换行
    ///     × code
    ///     × table
    ///     × tr
    ///     × ul
    ///     × ol
    ///  × 这些开始标签后增加缩进
    ///     × ul
    ///     × ol
    ///     × table
    ///     × tr
    ///  × 这些结束标签前减少缩进
    ///     × ul
    ///     × ol
    ///     × table
    ///     × tr
    /// </remarks>
    public class XmlDocFormatter : IFormatter
    {
        /// <summary>
        /// 初始化 XmlDocFormmater 类的新实例。
        /// </summary>
        /// <param name="writer"></param>
        public XmlDocFormatter(TextWriter writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            _Writer = writer;
        }
        
        private readonly TextWriter _Writer;

        /// <summary>
        /// Gets or sets the writer.
        /// </summary>
        /// <value>The writer.</value>
        public TextWriter Writer
        {
            get
            {
                return _Writer;
            }
        }

        /// <summary>
        /// 写入器当前的状态
        /// </summary>
        protected enum WriteState
        {
            /// <summary>
            /// 还未开始写入，或刚结束一个标签
            /// </summary>
            Start,
            /// <summary>
            /// 已经开始一个标签，可以写入属性，开始标签还未关闭
            /// </summary>
            Attribute,
            /// <summary>
            /// 开始标签已经关闭，但还未写入内容
            /// </summary>
            BeforeContent,
            /// <summary>
            /// 已经写入一部分内容，但还未关闭当前标签
            /// </summary>
            Content,
        }

        // 写入器当前的状态
        private WriteState _State = WriteState.Start;

        /// <summary>
        /// 写入器当前的状态
        /// </summary>
        /// <value>The state.</value>
        protected WriteState State
        {
            get { return _State; }
            set { _State = value; }
        }

        // 当前还未完成的标签，一旦标签的开始标志关闭，本字段就被置为空值
        private string _CurrentTag;

        /// <summary>
        /// Begins the tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        public virtual void BeginTag(string tag)
        {
            if (tag == null) throw new ArgumentNullException("tag");

            if (_State == WriteState.Attribute) CloseBeginTag();

            switch (tag)
            {
                case "ol":
                case "ul":
                    _ListLevel++;
                    if (_IndentLevel > 0) WriteLine();
                    break;
                case "br":
                    return;
            }

            Write('<');

            Write(tag);

            _CurrentTag = tag;
            _State = WriteState.Attribute;
        }

        /// <summary>
        /// Adds the attribute.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public virtual void AddAttribute(string name, string value)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (value == null) throw new ArgumentNullException("value");

            System.Diagnostics.Debug.Assert(_State == WriteState.Attribute);

            // value 中的双引号要被替换为连续的双引号
            Write(string.Format(" {0}=\"{1}\"", name, value.Replace("\"", "\"\"")));
        }

        /// <summary>
        /// Closes the begin tag.
        /// </summary>
        private void CloseBeginTag()
        {
            System.Diagnostics.Debug.Assert(_CurrentTag != null);

            Write('>');

            switch (_CurrentTag)
            {
                case "code":
                case "table":
                case "tr":
                case "ul":
                case "ol":
                    WriteLine();
                    break;
            }

            switch (_CurrentTag)
            {
                case "table":
                case "tr":
                case "ul":
                case "ol":
                    Indent();
                    break;
            }

            _CurrentTag = null;
            _State = WriteState.BeforeContent;
        }

        /// <summary>
        /// Ends the tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        public virtual void EndTag(string tag)
        {
            if (tag == null) throw new ArgumentNullException("tag");

            if (tag == "br") return;

            if (_State == WriteState.Attribute)
            {
                Write("/>");
            }
            else
            {
                switch (tag)
                {
                    case "ol":
                    case "ul":
                    case "table":
                    case "tr":
                        Unindent();
                        break;
                    case "code":
                        WriteLine();
                        break;
                }

                Write(string.Format("</{0}>", tag));
            }

            switch (tag)
            {
                case "ol":
                case "ul":
                    _ListLevel--;
                    break;
            }

            switch (tag)
            {
                default:
                    WriteLine();
                    break;
            }

            _CurrentTag = null;
            _State = WriteState.Start;
        }

        /// <summary>
        /// Appends the content.
        /// </summary>
        /// <param name="str">The STR.</param>
        public void AppendContent(string str)
        {
            if (str == null) throw new ArgumentNullException("str");

            System.Diagnostics.Debug.Assert(_State != WriteState.Start);

            if (_State == WriteState.Attribute)
            {
                CloseBeginTag();
            }
            
            if (_State == WriteState.BeforeContent)
            {
                Write(str);
            }
            else
            {
                WriteLine();

                if (_ListLevel > 0)
                {
                    Indent();
                    Write(str);
                    Unindent();
                }
                else
                {
                    Write(str);
                }
            }

            _State = WriteState.Content;
        }

        private int _IndentSize = 3;
        /// <summary>
        /// Gets or sets the size of the indent.
        /// </summary>
        /// <value>The size of the indent.</value>
        public int IndentSize
        {
            get
            {
                return _IndentSize;
            }
            set
            {
                if (value < 0) throw new ArgumentException("IndentSize 必须大于等于 0", "value");

                _IndentSize = value;
            }
        }

        private int _ListLevel;
        /// <summary>
        /// 当前列表嵌套级别
        /// </summary>
        /// <value>The list level.</value>
        public int ListLevel
        {
            get { return _ListLevel; }
        }

        private int _IndentLevel;
        /// <summary>
        /// Gets or sets the indent level.
        /// </summary>
        /// <value>The indent level.</value>
        public int IndentLevel
        {
            get
            {
                return _IndentLevel;
            }
            set
            {
                if (value < 0) throw new ArgumentException("IndentLevel 必须大于等于 0", "value");

                _IndentLevel = value;
            }
        }

        /// <summary>
        /// Indents this instance.
        /// </summary>
        public void Indent()
        {
            _IndentLevel ++;
        }

        /// <summary>
        /// Unindents this instance.
        /// </summary>
        public void Unindent()
        {
            _IndentLevel --;
            if (_IndentLevel < 0) _IndentLevel = 0;
        }

        private bool _inLineBegin = true;

        /// <summary>
        /// Writes the specified ch.
        /// </summary>
        /// <param name="ch">The ch.</param>
        private void Write(char ch)
        {
            WriteIndent();

            _Writer.Write(ch);
        }

        /// <summary>
        /// Writes the indent.
        /// </summary>
        private void WriteIndent()
        {
            if (_inLineBegin)
            {
                _inLineBegin = false;

                for (int i = 0; i < _IndentLevel * _IndentSize; i++)
                {
                    _Writer.Write(' ');
                }
            }
        }

        /// <summary>
        /// Writes the specified string.
        /// </summary>
        /// <param name="s">The s.</param>
        private void Write(string s)
        {
            if (s.Length == 0) return;

            WriteIndent();

            _Writer.Write(s);
        }

        /// <summary>
        /// Writes the space line.
        /// </summary>
        private void WriteLine()
        {
            _Writer.WriteLine();
            _inLineBegin = true;
        }
    }
}
